//
// snake_move.c
// Created by Yuxuan Liao on 12/20/2020
//

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>

#define SNAKE_MAX_LENGTH 20
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define WALL_CELL '*'
#define FOOD '$'
#define MOVE_UP 'W'
#define MOVE_DOWN 'S'
#define MOVE_LEFT 'A'
#define MOVE_RIGHT 'D'
#define PAUSE_GAME 'P'
#define CONTINUE_GAME 'C'
#define EXIT_GAME 'Q'
#define GAME_OVER "GAME OVER!\n"
#define UP 0, -1
#define DOWN 0, 1
#define LEFT -1, 0
#define RIGHT 1, 0

#define bool int
#define true 1
#define false 0
#define HEAD ((snakeLength)-1)
#define TAIL 0

// initial the map with snake
char map[13][13] = {
    "************",
    "*XXXXH     *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "*          *",
    "************",
};

// the position of the snake, TAIL = 0, HEAD = snakeLength - 1
int snakeRow[SNAKE_MAX_LENGTH] = {1, 1, 1, 1, 1};
int snakeCol[SNAKE_MAX_LENGTH] = {1, 2, 3, 4, 5};

// the length of the snake
int snakeLength = 5;

// make the snake move
void SnakeMove(int dx, int dy);

// print the map and the snake
void PrintMap();

// check whether the snake eats the food:
bool isEaten();

// return true if the snake hit itself of hit the wall, else return false
bool checkHitItself();

// print GameOver
void GameOver();

// return true if the snake is still in the map, else return false
bool checkNotHitWall();

// clear the terminal
void clearTerminal();

int main()
{
    srand(time(NULL));
    clearTerminal();
    PrintMap();

    bool wrongInput = false, paused = false;
    while (true)
    {
        if (!paused)
            printf("Press WASD to move:\n");
        // input the command
        char input = getchar();
        input = toupper(input);
        // get the enter
        getchar();

        // if the game is paused
        if (paused)
        {
            if (input == EXIT_GAME)
            {
                printf("You've exited the game\n");
                exit(0);
            }

            if (input != CONTINUE_GAME)
            {
                printf("Press C to continue.\n");
                continue;
            }
        }

        switch (input)
        {
        case MOVE_UP:
            SnakeMove(UP);
            break;

        case MOVE_DOWN:
            SnakeMove(DOWN);
            break;

        case MOVE_LEFT:
            SnakeMove(LEFT);
            break;

        case MOVE_RIGHT:
            SnakeMove(RIGHT);
            break;

        case PAUSE_GAME:
            paused = true;
            clearTerminal();
            printf("\nThe game is paused. Press C to continue.\n");
            break;

        case CONTINUE_GAME:
            if (!paused)
            {
                wrongInput = true;
                break;
            }
            else if (paused)
            {
                printf("Continue.\n");
                paused = false;
                continue;
            }

        case EXIT_GAME:
            printf("You have exited the game\n");
            exit(0);

        // if the input is wrong, then do nothing
        default:
            wrongInput = true;
            break;
        }

        // check whether it is a wrong input
        if (wrongInput == true)
        {
            printf("Wrong input. Please retry.\n");
            wrongInput = false;
        }
        else
        {
            PrintMap();
            if (checkHitItself())
            {
                GameOver();
                break;
            }
        }
    }

    return 0;
}

// make the snake move
void SnakeMove(int dx, int dy)
{
    int newTailX = snakeCol[TAIL];
    int newTailY = snakeRow[TAIL];

    // row: snakeRowY, column: snakeCol
    map[snakeRow[TAIL]][snakeCol[TAIL]] = BLANK_CELL;
    map[snakeRow[HEAD]][snakeCol[HEAD]] = SNAKE_BODY;

    // move forward the body
    for (int body = TAIL; body < HEAD; body++)
    {
        snakeCol[body] = snakeCol[body + 1];
        snakeRow[body] = snakeRow[body + 1];
    }

    snakeCol[HEAD] += dx;
    snakeRow[HEAD] += dy;
    map[snakeRow[HEAD]][snakeCol[HEAD]] = SNAKE_HEAD;
    clearTerminal();
}

// print the map and the snake
void PrintMap()
{
    for (int i = 0; i < 12; i++)
    {
        printf("%s\n", map[i]);
    }
}

// return true if the snake hit itself of hit the wall, else return false
bool checkHitItself()
{
    bool hitBody = false;
    // check whether the snake head if touches the body
    for (int body = TAIL; body < HEAD - 1; body++)
    {
        if (snakeRow[body] == snakeRow[HEAD] && snakeCol[HEAD] == snakeCol[body])
        {
            hitBody = true;
        }
    }
    // check whether the snake head if touches the wall
    return hitBody || !checkNotHitWall();
}

// print GameOver
void GameOver()
{
    printf(GAME_OVER);
}

// return true if the snake is still in the map, else return false
bool checkNotHitWall()
{
    return (snakeRow[HEAD] >= 1 && snakeRow[HEAD] <= 10) && (snakeCol[HEAD] >= 1 && snakeCol[HEAD] <= 10);
}

// clear the terminal
void clearTerminal()
{
    // system("cls");
    printf("\x1b[H\x1b[2J");
}